package mnet

/*
 * mnet service module.
 */

import (
	"net"
	"sync"
	"time"
)

// net event chan size
const (
	gEventChanSize   uint32 = 1024 * 2
	gMaxFuncChanSize uint32 = 1024 * 2
)

// mNet service
type mNet struct {
	// name
	name string

	// TCP version
	ipVersion string

	// Connection manager.
	connMgr IConnManager

	// Server event queue
	dealerQueue chan dealerEvent

	// close once flag
	closeOnce sync.Once

	// Max Connection，0 is unlimited.
	maxConnection uint32

	// attach
	attach interface{}

	// func executing chan
	evtChan chan *netEvent
}

/*
 * Create net service
 */
func newNetService() *mNet {
	if gEventChanSize <= 0 {
		panic("invalid deal event queue size")
	}
	netService := &mNet{
		name:          "NetService",
		ipVersion:     "tcp4",
		connMgr:       newConnManager(),
		dealerQueue:   make(chan dealerEvent, gEventChanSize),
		maxConnection: 0, // 0 is unlimited
		evtChan:       make(chan *netEvent, gMaxFuncChanSize),
	}
	// Init to start a goroutine to deal net event.
	netService.start()
	return netService
}

// start goroutine to process client connection.
func (mn *mNet) start() bool {
	// Start a goroutine to deal user connection request.
	go func() {
		for evt := range mn.dealerQueue {
			switch evt.dealType {
			case netDealerEventConnection:
				if !evt.connEvent.session.IsConnected() {
					addr := evt.connEvent.connAddr
					session := evt.connEvent.session
					codec := evt.connEvent.codec
					if session == nil || codec == nil {
						log().Error("[net] session is null or codec is nil")
						return
					}
					if !mn.syncDial(addr, session, codec) {
						if connErrCall := evt.connEvent.connErrCall; connErrCall != nil {
							connErrCall()
						}
						log().Error("[net] dial to ", addr, " error")
					}
				}
			}
		}
	}()

	return true
}

// Init max connection and max chan size.
func (mn *mNet) Init(maxConnectionSize uint32, log ILog, attach interface{}) bool {
	if log == nil {
		log = &myLog{}
	}
	// init inner log
	innerLog = log

	mn.maxConnection = maxConnectionSize
	mn.attach = attach
	return true
}

// Create server
func (mn *mNet) CreateServer(localAddr string,
	sessFactory ISessionFactory, codec ICodec,
	attach interface{}, lockThread bool, maxConn int,
) IServer {
	return newServer("tcp4", localAddr,
		sessFactory, codec, attach,
		lockThread, maxConn,
	)
}

// Create client
func (mn *mNet) CreateClient(remoteAddr string,
	session ISession, codec ICodec,
	attach interface{},
) IClient {
	return newClient(remoteAddr, session, codec, attach)
}

// Dial to remote address.
func (mn *mNet) Dial(addr string) *net.TCPConn {
	tcpAddr, err := net.ResolveTCPAddr(mn.ipVersion, addr)
	if err != nil {
		log().Error("[net] Resolve tcp addr error:", err)
		return nil
	}

	conn, dialErr := net.DialTCP(mn.ipVersion, nil, tcpAddr)
	if dialErr == nil {
		return conn
	} else {
		log().Error("[net] DialTCP error:", dialErr)
		return nil
	}
}

// sync dial to remote server address.
func (mn *mNet) syncDial(addr string, sesssion ISession, codec ICodec) bool {
	if sesssion == nil {
		return false
	}
	if codec == nil {
		return false
	}

	if conn := mn.Dial(addr); conn != nil {
		if tcpConn := mn.connMgr.create(mn, conn, sesssion, codec); tcpConn == nil {
			log().Error("[net] create connection is null")
			return false
		} else {
			tcpConn.start()
			return true
		}
	} else {
		return false
	}
}

// Dial with timeout
func (mn *mNet) dial(Addr string, timeOut time.Duration) *net.TCPConn {
	d := net.Dialer{Timeout: timeOut}
	conn, err := d.Dial(mn.ipVersion, Addr)
	if err == nil {
		return conn.(*net.TCPConn)
	} else {
		return nil
	}
}

// GetAllConnections gets all count.
func (mn *mNet) GetAllConnections() int {
	return mn.connMgr.len()
}

// add Dealer event interface
func (mn *mNet) addDealerEvent(evt dealerEvent) bool {
	mn.dealerQueue <- evt
	return true
}

// push event.
func (mn *mNet) push(evt *netEvent) {
	select {
	case mn.evtChan <- evt:
		return
	case <-time.After(time.Second * 5):
		log().Error("[net] push event timeout.")
		return
	}
}

// doEvt deal net event.
func (mn *mNet) doEvt(evt *netEvent) {
	if evt == nil {
		return
	}
	switch evt.evtType {
	case evtConnectType: // connection event
		evt.sink.OnStatus(true, evt.session)
	case evtDisconnectType: // disconnect event
		evt.sink.OnStatus(false, evt.session)
	case evtDataType: // receiving message event
		evt.sink.OnRecv(evt.msg)
	}
}

// Run net events.
func (mn *mNet) Run(count int) bool {
	var allCount int = 0
	for {
		select {
		case evt := <-mn.evtChan:
			if evt == nil {
				return false
			}

			mn.doEvt(evt)
			evtPool.Put(evt)

			if allCount++; allCount >= count {
				return true
			}
		default:
			return false
		}
	}
}

// Stop all net service
func (mn *mNet) Stop() {
	mn.closeOnce.Do(func() {
		// Stop.
		mn.connMgr.stop()

		// Wait to end
		mn.connMgr.wait()

		// release resource.
		close(mn.evtChan)
	})
}

// Get Connection manager
func (mn *mNet) getConnMgr() IConnManager {
	return mn.connMgr
}

func (mn *mNet) GetAttach() interface{} {
	return mn.attach
}

func (mn *mNet) GetMaxConn() uint32 {
	return mn.maxConnection
}

func (mn *mNet) MaxConnCheck() bool {
	return mn.maxConnection > 0 && uint32(mn.connMgr.len()) >= mn.maxConnection
}

// /////////////////////////////////////////////////////
// Global net instance.                              //
// ////////////////////////////////////////////////////
func GetNetInstance() INet {
	return gNetInstance
}

var gNetInstance *mNet

func init() { // global init for the net service.
	if gNetInstance = newNetService(); gNetInstance == nil {
		panic("[net] net instance is nil")
	}
}
